package com.tianji.learning.service.impl;


import cn.hutool.core.util.ObjectUtil;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

import static com.tianji.learning.enums.SectionType.VIDEO;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author waker
 * @since 2025-10-22
 */
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {

    private final ILearningLessonService learningLessonService;
    private final CourseClient courseClient;
    @Autowired
    private LearningRecordDelayTaskHandler delayTaskHandler;

    /**
     * 1. 查询当前用户指定课程的学习进度 - 供course微服务远程调用
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        // 1. 根据userId和courseId 查询课表数据(lessonId,lastestSectionId)
        Long userId = UserContext.getUser();
        LearningLesson lesson = learningLessonService.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if (ObjectUtil.isEmpty(lesson)) {
            return null;
        }
        Long lessonId = lesson.getId();
        Long lastestSectionId = lesson.getLatestSectionId();
        // 2. 查询学习记录数据
        List<LearningRecord> learningRecords = lambdaQuery()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getLessonId, lessonId)
                .list();

        // 3. 组装dto数据返回
        return LearningLessonDTO.of(lessonId, lastestSectionId, BeanUtils.copyList(learningRecords, LearningRecordDTO.class));
    }

    /**
     * 2. 提交学习记录
     *
     * @param formDTO
     */
    @Override
    @Transactional
    public void commitLearningRecord(LearningRecordFormDTO formDTO) {
        // 1. 处理学习记录(根据小节类型，判断处理考试/视频的学习记录)
        boolean isFirstFinished = formDTO.getSectionType() == VIDEO ? handleVideoRecord(formDTO) : handleExamRecord(formDTO);

        if (!isFirstFinished && formDTO.getDuration() == -1){
            return;
        }
        // 2. 处理课表数据
        handleLearningLesson(formDTO, isFirstFinished);
    }

    /**
     * 处理课表数据
     *
     * @param formDTO
     * @param isFirstFinished
     */
    private void handleLearningLesson(LearningRecordFormDTO formDTO, boolean isFirstFinished) {
        // 1. 查询课表数据（已学习小节数）
        LearningLesson lesson = learningLessonService.getById(formDTO.getLessonId());
        AssertUtils.isNotNull(lesson, "查询课表数据失败!");

        // 2. 查询课程的总课时数，判断全部小结是否已学完
        boolean allSessionsLearned = false;
        if (isFirstFinished) {
            // 2.1 查询课程的总课时数
            CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            AssertUtils.isNotNull(courseInfo, "查询课程信息失败!");

            // 2.2 比较已完成的小节数 + 1 是否 大于等于课程的总课时数
            allSessionsLearned = courseInfo.getSectionNum() <= lesson.getLearnedSections() + 1;
        }

        // 3. 更新课表数据
        boolean result = learningLessonService.lambdaUpdate()
                .set(LearningLesson::getLatestSectionId, formDTO.getSectionId())
                .set(LearningLesson::getLatestLearnTime, formDTO.getCommitTime())
                .set(isFirstFinished, LearningLesson::getLearnedSections, lesson.getLearnedSections() + 1)
                /*.setSql("learned_sections = learned_sections + 1")*/
                .set(allSessionsLearned, LearningLesson::getStatus, LessonStatus.FINISHED)
                .set(lesson.getStatus() == LessonStatus.NOT_BEGIN, LearningLesson::getStatus, LessonStatus.LEARNING)
                .eq(LearningLesson::getId, formDTO.getLessonId())
                .update();

        if (!result) {
            throw new DbException("更新课表数据失败!");
        }

    }

    /**
     * 处理考试学习记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleExamRecord(LearningRecordFormDTO formDTO) {
        // 1. 将formDTO换成po
        LearningRecord learningRecord = BeanUtils.copyBean(formDTO, LearningRecord.class);

        // 2. 补全po数据
        learningRecord.setUserId(UserContext.getUser());
        learningRecord.setFinished(true);
        learningRecord.setFinishTime(formDTO.getCommitTime());

        // 3. 保存学习记录到数据库
        boolean result = save(learningRecord);
        if (!result) {
            throw new DbException("新增考试学习记录失败!");
        }

        // 4. 返回是否是第一次学完的标识
        return true;
    }

    /**
     * 处理视频学习记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleVideoRecord(LearningRecordFormDTO formDTO) {
        // 1. 查询旧的学习记录
        LearningRecord oldLearningRecord = queryOldRecord(formDTO.getLessonId(), formDTO.getSectionId());

        // 2. 判断是否存在视频学习记录
        if (ObjectUtil.isEmpty(oldLearningRecord)) {
            // 3. 如果不存在，则新增学习记录
            // 3.1 将formDTO换成po
            LearningRecord learningRecord = BeanUtils.copyBean(formDTO, LearningRecord.class);

            // 3.2 补全po数据 视频第一次来一定没有学完
            learningRecord.setUserId(UserContext.getUser());

            // 3.3 保存学习记录到数据库
            boolean result = save(learningRecord);
            if (!result) {
                throw new DbException("新增视频学习记录失败!");
            }
            // 3.4 返回是否是第一次学完的标识
            return false;
        }

        // 4. 如果存在，则更新学习记录
        // 4.1 判断是否是第一次学完当前小节
        boolean firstFinished = !oldLearningRecord.getFinished() && formDTO.getMoment() << 1 > formDTO.getDuration();

        // 4.2 如果是非第一次学完，将学习记录暂存到redis，并提交一个延时任务
        if (!firstFinished) {
            LearningRecord learningRecord = new LearningRecord();
            learningRecord.setId(oldLearningRecord.getId());
            learningRecord.setMoment(formDTO.getMoment());
            learningRecord.setFinished(oldLearningRecord.getFinished());
            learningRecord.setLessonId(formDTO.getLessonId());
            learningRecord.setSectionId(formDTO.getSectionId());
            delayTaskHandler.addLearningRecordAndDelayCheckTask(learningRecord);
            formDTO.setDuration(-1);
            return false;
        }

        // 4.3 如果是第一次学完 更新学习记录
        boolean result = this.lambdaUpdate()
                .set(LearningRecord::getMoment, formDTO.getMoment())
                .set(/*firstFinished,*/ LearningRecord::getFinished, firstFinished)
                .set(/*firstFinished,*/ LearningRecord::getFinishTime, formDTO.getCommitTime())
                .eq(LearningRecord::getId, oldLearningRecord.getId())
                .update();
        if (!result) {
            throw new DbException("更新视频学习记录失败!");
        }
        //4.4 删除redis缓存,保证redis Mysql数据双写一致性
        delayTaskHandler.removeLearningRecordCache(formDTO.getLessonId(), formDTO.getSectionId());

        // 5. 返回是否是第一次学完的标识
        return firstFinished;
    }

    /**
     * 查询旧学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        // 1. 先从缓存中查询旧学习记录
        LearningRecord oldRecord = delayTaskHandler.readLearningRecordFromCache(lessonId, sectionId);

        // 2. 判断缓存中是否存在数据，如果存在，直接返回
        if (ObjectUtil.isNotEmpty(oldRecord)) {
            return oldRecord;
        }

        //  3. 如果不存在，则从数据库中查询旧学习记录,并将结果放入缓存
        // select * from learning_record where lesson_id = ? and section_id = ?
        oldRecord = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId)
                .one();
        if (ObjectUtil.isEmpty(oldRecord)) {
            return null;
        }
        delayTaskHandler.writeLearningRecordToCache(oldRecord);

        // 4. 返回数据
        return oldRecord;
    }

}
